feat: content-blind push notifications — daemon backbone (P2 push, slice 1) - #249
Merged
Conversation
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
…ice 1)
When a session blocks on a tool approval, the daemon now sends a
CONTENT-BLIND wake-up to the session owner's registered devices off-LAN,
behind a swappable PushTransport seam. Expo Push is the v1 transport; a
self-hosted content-blind relay swaps in behind the same interface later
(the migration is a transport change, not a redesign). codeoid-mobile
consumes this after the next codeoid release — this slice is the
headless, device-free backbone.
Protocol (@codeoid/protocol):
- push.register / push.unregister client messages + a PUSH capability +
PUSH_TOKEN_MAX limit; zod schemas + the compile-time coverage samples.
Daemon:
- push_registrations SQLite table + owner-scoped CRUD, keyed on the
ZeroID identity (owner_sub == sessions.created_by), tenant-scoped by
account/project — so only the session's creator is alerted, never
across tenants.
- PushTransport seam (src/daemon/push): ExpoPushTransport (batched at
100/req, bare fetch, best-effort/never-throws) + a noop transport,
selected by config.push.transport ("expo" | "none", default "none").
- PushService.notifyApproval resolves a blocked session's owner to their
devices and delivers a content-blind { sessionId, kind } note — never
a tool name, args, or description.
- Emit at SessionManager.#statusObserver — the single daemon-wide
chokepoint that fires regardless of client attachment — gated on
waiting_approval; fire-and-forget so a push hiccup can't touch the
status path.
- push.register / push.unregister handlers, self-scoped to the caller's
auth (no bespoke scope, mirroring session.ui_response).
- The daemon advertises the PUSH capability on auth.ok only when a
transport is configured, so clients feature-detect push support.
Config: a push block + CODEOID_PUSH_TRANSPORT / CODEOID_EXPO_ACCESS_TOKEN
env overrides.
Tests (bun): push_registrations store (owner/tenant isolation, refresh,
owner-scoped delete); Expo transport content-blindness + batching + auth
header + error-swallow; the createPushTransport factory; PushService
routing; register/unregister handlers over the wire; and an end-to-end
emit test that drives a real session to waiting_approval and asserts the
outbound Expo payload is content-blind.
Signed-off-by: Yash Datta <yd2590@columbia.edu>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
saucam
force-pushed
the
feat/push-notifications
branch
from
July 26, 2026 03:48
9723333 to
db031dd
Compare
KunalJavelin
approved these changes
Jul 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Push notifications — the daemon backbone (P2 push, slice 1 of 3). When a session blocks on a tool approval, the daemon now sends a content-blind wake-up to the session owner's registered devices off-LAN, behind a swappable
PushTransportseam. Expo Push is the v1 transport; a self-hosted content-blind relay swaps in behind the same interface later.This is the headless, device-free backbone.
codeoid-mobileconsumes it after the next codeoid release (slice 2); the self-hosted relay + iOS NSE + background Approve/Deny are slice 3.Design
SessionManager.#statusObserver) only ever has(sessionId, status)in hand, and the payload carries only{ sessionId, kind }+ generic copy — never a tool name, args, or description. Even through a third-party transport (Expo), no session content leaves the daemon; the app resolves human-readable context over its authenticated socket on tap.PushTransport(src/daemon/push) is the seam:ExpoPushTransport(v1) → a self-hosted relay (.p8APNs + FCM + NSE poll-back) later, with no redesign.config.push.transport: "expo" | "none"(defaultnone).push_registrationskeys on the ZeroID identity (owner_sub == sessions.created_by), tenant-scoped by account/project — only the session's creator is alerted, never across tenants.Surface
push.register/push.unregister+ aPUSHcapability (advertised onauth.okonly when a transport is configured, so clients feature-detect).push_registrationsstore + owner-scoped CRUD;PushService; the emit; self-scoped handlers (no bespoke scope, mirroringsession.ui_response).pushblock +CODEOID_PUSH_TRANSPORT/CODEOID_EXPO_ACCESS_TOKENenv overrides.Tests
bun test: store owner/tenant isolation + refresh + owner-scoped delete; Expo content-blindness + batching + auth header + error-swallow; the transport factory;PushServicerouting; register/unregister over the wire; and an end-to-end emit test that drives a real session towaiting_approvaland asserts the outbound Expo payload is content-blind. Lint + typecheck clean; a regression net over protocol / core / config / session-manager is green.Follow-ups
codeoid-mobile, after a release):expo-notifications+ register-on-connect + tap→approval + EAS dev build. First push lands on a phone here.🤖 Generated with Claude Code